﻿using System;
using System.Data;
using System.Collections;
using System.Text;
using System.Configuration;

namespace BizElements.Core
{
    #region Documentation.
    /// <summary>
    /// Compares two items.
    /// </summary>
    /// <remarks><p>ComparePredicate cannot be created by using its constructor. 
    /// Use <see cref="PredicateFactory"/> to create predicates.</p>
    /// <p>See <see cref="ComparisonOperator"/> for the list of supported comparison operators.</p>
    /// <seealso cref="SearchCondition"/></remarks>
    /// <example>
    /// The following example selects all employees who were hired before Janury 1, 2000.
    /// <code>
    /// public DataTable SelectEmployeesHiredBefore2000()
    /// {
    /// 	EmployeesMeta employees = new EmployeesMeta();
    /// 	SelectStatement select = new SelectStatement(employees);
    /// 	select.SelectList.Add(employees.Columns);
    /// 	select.Where.Add(PredicateFactory.Compare(employees.HireDate, "&lt;", new DateTime(2000, 1, 1)));
    /// 	return select.Execute();
    /// }
    /// </code>
    /// </example>
    #endregion
    [Serializable]
    public sealed class ComparePredicate : PredicateBase
    {
        #region Arithmetic comparison.

        /// <summary>
        /// Comparison operator. Eg: ">=".
        /// </summary>
        string comparisonOperator;

        /// <summary>
        /// Creates a new comparison predicate.
        /// </summary>
        /// <param name="lhs">Left hand side item.</param>
        /// <param name="comparisonOperator"><see cref="ComparisonOperator"/>. Eg: ">=".</param>
        /// <param name="rhs">Right hand side item.</param>
        /// <param name="negate">Specifies whether the predicate is negated (NOT).</param>
        internal ComparePredicate(PredicateItem lhs, string comparisonOperator, PredicateItem rhs, bool negate)
        {
            PredicateItems = new PredicateItem[] { lhs, rhs };
            Negate = negate;
            this.comparisonOperator = comparisonOperator;
        }

        /// <summary>
        /// Renders predicate as SQL statement.
        /// </summary>
        /// <param name="dbms">Target DBMS.</param>
        /// <param name="output">StringBuilder to which SQL is appended.</param>
        /// <param name="parameters">SQL parameter collection to which the object's and its children's
        /// parameters are added. After the rendering is done the collection contains all parameters with unique names.</param>
        public override void Render(DbmsType dbms, StringBuilder output, DbParameterCollection parameters)
        {
            output.Append("(");
            if (Negate)
                output.Append("NOT (");

            PredicateItems[0].Render(dbms, output, parameters);

            output.Append(" ");
            string ansiCompliantOp = TranslateToAnsiSqlCompliantOperator(this.comparisonOperator);
            output.Append(ansiCompliantOp);
            output.Append(" ");

            PredicateItems[1].Render(dbms, output, parameters);

            if (Negate)
                output.Append(")");
            output.Append(")");
        }

        #endregion

        private static string TranslateToAnsiSqlCompliantOperator(string comparisonOperator)
        {
            foreach (string ansiOp in AnsiOperators)
            {
                if (ansiOp == comparisonOperator)
                    return comparisonOperator;
            }

            if (comparisonOperator == "!=")
                return ComparisonOperator.NotEqual;

            throw new ArgumentException("Specified '{0}' operator is not valid. Only operators enumerated in 'ComparisonOperator' may be used.", "comparisonOperator");
        }

        static readonly string[] AnsiOperators = new string[] 
        { 
            ComparisonOperator.Equal, 
            ComparisonOperator.NotEqual, 
            ComparisonOperator.LessEqual, 
            ComparisonOperator.LesserThan, 
            ComparisonOperator.GreaterEqual, 
            ComparisonOperator.GreaterThan 
        };
    }

    #region Documentation.
    /// <summary>
    /// Defines comparison operators that may be used in a <see cref="ComparePredicate"/>.
    /// </summary>
    /// <remarks><see cref="PredicateFactory.Compare(IDbColumn, string, object)"/> method of the <see cref="PredicateFactory"/> class 
    /// accepts a string value for the comparison operator parameter. Use the operators enumerated in this class 
    /// if you are not comfortable with using hard-coded strings in your code.</remarks>
    /// <example>
    /// The folowing example demostrates how to use the ComparisonOperator class to create a ComparePredicate.
    /// <code>
    /// EmployeesMeta employees = new EmployeesMeta();
    /// ComparePredicate lessThan10 = PredicateFactory.Compare(employees.EmployeeID, "&lt;", 10);
    /// 
    /// // This is the same as the above line but uses string constants enumerated in the ComparisonOperator class.
    /// lessThan10 = PredicateFactory.Compare(employees.EmployeeID, ComparisonOperator.LesserThan, 10);
    /// </code>
    /// </example>
    #endregion
    public static class ComparisonOperator
    {
        #region Operators.

        /// <summary>
        /// "=" operator.
        /// </summary>
        public const string Equal = "=";

        /// <summary>
        /// "&lt;&gt;" operator.
        /// </summary>
        public const string NotEqual = "<>";

        /// <summary>
        /// "&lt;=" operator.
        /// </summary>
        public const string LessEqual = "<=";

        /// <summary>
        /// "&lt;" operator.
        /// </summary>
        public const string LesserThan = "<";

        /// <summary>
        /// "&gt;=" operator.
        /// </summary>
        public const string GreaterEqual = ">=";

        /// <summary>
        /// "&gt;" operator.
        /// </summary>
        public const string GreaterThan = ">";

        #endregion        
    }
}
